Open addressing, or closed hashing, is a method of collision resolution in hash tables. With this method a hash collision is resolved by probing, or searching through alternative locations in the array (the probe sequence) until either the target record is found, or an unused array slot is found, which indicates that there is no such key in the table. Well-known probe sequences include:
The main trade offs between these methods are that linear probing has the best cache performance but is most sensitive to clustering, while double hashing has poor cache performance but exhibits virtually no clustering; quadratic probing falls in between in both areas. Double hashing can also require more computation than other forms of probing.
Some open addressing methods, such as Hopscotch hashing, Robin Hood hashing, last-come-first-served hashing and cuckoo hashing move existing keys around in the array to make room for the new key. This gives better maximum search times than the methods based on probing. Poblete; Viola; Munro. "The Analysis of a Hashing Scheme by the Diagonal Poisson Transform". p. 95 of Jan van Leeuwen (Ed.) "Algorithms - ESA '94". 1994. Steve Heller. "Efficient C/C++ Programming: Smaller, Faster, Better" 2014. p. 33. Patricio V. Poblete, Alfredo Viola. "Robin Hood Hashing really has constant average search cost and variance in full tables". 2016. Paul E. Black, "Last-Come First-Served Hashing", in Dictionary of Algorithms and Data Structures online, Vreda Pieterse and Paul E. Black, eds. 17 September 2015. Paul E. Black, "Robin Hood hashing", in Dictionary of Algorithms and Data Structures online, Vreda Pieterse and Paul E. Black, eds. 17 September 2015.
A critical influence on performance of an open addressing hash table is the load factor; that is, the proportion of the slots in the array that are used. As the load factor increases towards 100%, the number of probes that may be required to find or insert a given key rises dramatically. Once the table becomes full, probing algorithms may even fail to terminate. Even with good hash functions, load factors are normally limited to 80%. A poor hash function can exhibit poor performance even at very low load factors by generating significant clustering, especially with the simplest linear addressing method. Generally typical load factors with most open addressing methods are 50%, while separate chaining typically can use up to 100%.
'''record''' pair { key, value, occupied flag (initially unset) } '''var''' pair slot[0], slot[1], ..., slot[num_slots - 1]
'''function''' find_slot(key) i := hash(key) modulo num_slots ''// search until we either find the key, or find an empty slot.'' '''while''' (slot[i] is occupied) and (slot[i].key ≠ key) i := (i + 1) modulo num_slots '''return''' i
'''function''' lookup(key) i := find_slot(key) '''if''' slot[i] is occupied ''// key is in table'' '''return''' slot[i].value '''else''' ''// key is not in table'' '''return''' not found
'''function''' set(key, value) i := find_slot(key) '''if''' slot[i] is occupied ''// we found our key'' slot[i].value := value '''return''' '''if''' the table is almost full rebuild the table larger ''(note 1)'' i := find_slot(key) mark slot[i] as occupied slot[i].key := key slot[i].value := value
'''function''' remove(key) i := find_slot(key) '''if''' slot[i] is unoccupied '''return''' ''// key is not in the table'' mark slot[i] as unoccupied j := i '''loop''' ''(note 2)'' j := (j + 1) modulo num_slots '''if''' slot[j] is unoccupied '''exit loop''' k := hash(slot[j].key) modulo num_slots ''// determine if k lies cyclically in (i,j]'' ''// i ≤ j: | i..k..j |'' ''// i > j: |.k..j i....| or |....j i..k.|'' '''if''' i ≤ j '''if''' (i < k) and (k ≤ j) '''continue loop''' '''else''' '''if''' (k ≤ j) or (i < k) '''continue loop''' mark slot[i] as occupied slot[i].key := slot[j].key slot[i].value := slot[j].value mark slot[j] as unoccupied i := j
Another technique for removal is simply to mark the slot as deleted. However this eventually requires rebuilding the table simply to remove deleted records. The methods above provide O(1) updating and removal of existing records, with occasional rebuilding if the high-water mark of the table size grows.
The O(1) remove method above is only possible in linearly probed hash tables with single-slot stepping. In the case where many records are to be deleted in one operation, marking the slots for deletion and later rebuilding may be more efficient.
Here n is the number of elements in the table, m the table size, the load factor, the number of probes in an unsucessful search and the number of probes in a successful search.
Note that the expectation deteriorates to infinity when the load factor approaches 1.
|
|